
#include "filesystem_monitor.h"
#include "utils.h"

#include <ctype.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <unistd.h>

void bb_filesystem_monitor_init(
        struct bb_filesystem_monitor* fm,
        void (*handler)(void*, struct bb_filesystem_change const*),
        void* handler_arg0,
        struct bb_view* view)
{
    fm->handler = handler;
    fm->handler_arg0 = handler_arg0;
    fm->view = view;
    fm->inotify_fd = inotify_init();
    if (fm->inotify_fd <= 0)
        bb_fatal_errno("inotify_init failed");
}

void bb_filesystem_monitor_uninit(struct bb_filesystem_monitor* fm)
{
    close(fm->inotify_fd);
    fm->handler = NULL;
    fm->handler_arg0 = NULL;
    fm->inotify_fd = 0;
}

void bb_filesystem_monitor_add_directory(
        struct bb_filesystem_monitor* fm, char const* directory)
{
    char* const buffer = malloc(strlen(directory) + 512);
    sprintf(buffer, "watching directory '%s'", directory);
    bb_view_on_control_output(fm->view, buffer);
    int result = inotify_add_watch(fm->inotify_fd, directory, IN_CLOSE_WRITE);
    if (result < 0)
        bb_fatal_errno("inotify_add_watch failed");

    // recurse to subdirectories
    DIR* dir = opendir(directory);
    struct dirent* de;
    while ( (de = readdir(dir)) )
    {
        if (de->d_type != DT_DIR || bb_is_file_or_dir_hidden(de->d_name))
            continue;
        sprintf(buffer, "%s/%s", directory, de->d_name);
        bb_filesystem_monitor_add_directory(fm, buffer);
    }
    free(buffer);
}

void bb_filesystem_monitor_run(struct bb_filesystem_monitor* fm)
{
    static size_t const BB_MAX_NAME_LEN = 128;
    size_t const buffer_len = 32 * (sizeof(struct inotify_event) + BB_MAX_NAME_LEN) + 1;
    char* const buffer = malloc(buffer_len);
    char announcement_buffer[512];

    while (true)
    {
        int result = read(fm->inotify_fd, buffer, buffer_len);
        if (result <= 0)
            bb_fatal_errno("read(inotify_fd) failed");
        char* const buffer_end = buffer + result;
        char* buffer_next = buffer;
        while (buffer_next != buffer_end)
        {
            struct inotify_event* const event = (struct inotify_event*)buffer_next;
            if (event->len && !bb_is_file_or_dir_hidden(event->name) &&
                bb_is_source_file(event->name))
            {
                sprintf(announcement_buffer, "'%.*s' updated", (int)event->len, event->name);
                //bb_announce_event(&now, 0, announcement_buffer);
                bb_view_on_control_output(fm->view, announcement_buffer);
                struct bb_filesystem_change change;
                fm->handler(fm->handler_arg0, &change);
                break;
            }
            buffer_next += sizeof(struct inotify_event) + event->len;
        }
    }
}

